/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on 28/08/2005
*/
package com.python.pydev.analysis.visitors;
import java.util.HashMap;
import java.util.Map;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.structure.FastStack;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.editor.codecompletion.revisited.visitors.AbstractVisitor;
import org.python.pydev.parser.jython.ast.Assign;
import org.python.pydev.parser.jython.ast.Call;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.jython.ast.decoratorsType;
import org.python.pydev.parser.jython.ast.exprType;
import org.python.pydev.parser.visitors.NodeUtils;
import com.aptana.shared_core.string.FastStringBuffer;
import com.aptana.shared_core.structure.Tuple;
import com.python.pydev.analysis.IAnalysisPreferences;
public final class NoSelfChecker {
public static class Expected {
public String expected;
public String received;
public Expected(String expected, String received) {
this.expected = expected;
this.received = received;
}
}
private final FastStack<Integer> scope = new FastStack<Integer>(10);
private final FastStack<HashMap<String, Tuple<Expected, FunctionDef>>> maybeNoSelfDefinedItems = new FastStack<HashMap<String, Tuple<Expected, FunctionDef>>>(
10);
/**
* Stack with the names of the classes
*/
private FastStack<String> classBases = new FastStack<String>(10);
private final String moduleName;
private final MessagesManager messagesManager;
public NoSelfChecker(OccurrencesVisitor visitor) {
this.messagesManager = visitor.messagesManager;
this.moduleName = visitor.moduleName;
scope.push(Scope.SCOPE_TYPE_GLOBAL); //we start in the global scope
}
public void beforeClassDef(ClassDef node) {
scope.push(Scope.SCOPE_TYPE_CLASS);
FastStringBuffer buf = new FastStringBuffer();
for (exprType base : node.bases) {
if (buf.length() > 0) {
buf.append(",");
}
String rep = NodeUtils.getRepresentationString(base);
if (rep != null) {
buf.append(FullRepIterable.getLastPart(rep));
}
}
classBases.push(buf.toString());
maybeNoSelfDefinedItems.push(new HashMap<String, Tuple<Expected, FunctionDef>>());
}
public void afterClassDef(ClassDef node) {
scope.pop();
classBases.pop();
creteMessagesForStack(maybeNoSelfDefinedItems);
}
/**
* @param stack
* @param shouldBeDefined
*/
private void creteMessagesForStack(FastStack<HashMap<String, Tuple<Expected, FunctionDef>>> stack) {
HashMap<String, Tuple<Expected, FunctionDef>> noDefinedItems = stack.pop();
for (Map.Entry<String, Tuple<Expected, FunctionDef>> entry : noDefinedItems.entrySet()) {
Expected expected = entry.getValue().o1;
if (!expected.expected.equals(expected.received)) {
SourceToken token = AbstractVisitor.makeToken(entry.getValue().o2, moduleName);
messagesManager.addMessage(IAnalysisPreferences.TYPE_NO_SELF, token,
new Object[] { token, entry.getValue().o1.expected });
}
}
}
/**
* when a class is declared inside a function scope, it must start with self if it does
* not start with the self parameter, unless it has a staticmethod decoration or is
* later assigned to a staticmethod.
*
* @param node
*/
public void beforeFunctionDef(FunctionDef node) {
if (scope.peek().equals(Scope.SCOPE_TYPE_CLASS)) {
//let's check if we have to start with self or cls
boolean startsWithSelf = false;
boolean startsWithCls = false;
String received = "";
if (node.args != null) {
if (node.args.args.length > 0) {
exprType arg = node.args.args[0];
if (arg instanceof Name) {
Name n = (Name) arg;
if (n.id.equals("self")) {
startsWithSelf = true;
} else if (n.id.equals("cls")) {
startsWithCls = true;
}
received = n.id;
}
}
}
boolean isStaticMethod = false;
boolean isClassMethod = false;
if (node.decs != null) {
for (decoratorsType dec : node.decs) {
if (dec != null) {
String rep = NodeUtils.getRepresentationString(dec.func);
if (rep != null) {
if (rep.equals("staticmethod")) {
isStaticMethod = true;
} else if (rep.equals("classmethod")) {
isClassMethod = true;
}
}
}
}
}
//didn't have staticmethod decorator either
String rep = NodeUtils.getRepresentationString(node);
if (rep.equals("__new__")) {
//__new__ could start wit cls or self
if (!startsWithCls && !startsWithSelf) {
maybeNoSelfDefinedItems.peek().put(rep,
new Tuple<Expected, FunctionDef>(new Expected("self or cls", received), node));
}
} else if (!startsWithSelf && !startsWithCls && !isStaticMethod && !isClassMethod) {
maybeNoSelfDefinedItems.peek().put(rep,
new Tuple<Expected, FunctionDef>(new Expected("self", received), node));
} else if (startsWithCls && !isClassMethod && !isStaticMethod) {
String classBase = classBases.peek();
if (rep.equals("__init__") && "type".equals(classBase)) {
//ok, in this case, cls is expected
} else {
maybeNoSelfDefinedItems.peek().put(rep,
new Tuple<Expected, FunctionDef>(new Expected("self", received), node));
}
}
}
scope.push(Scope.SCOPE_TYPE_METHOD);
}
public void afterFunctionDef(FunctionDef node) {
scope.pop();
}
public void visitAssign(Assign node) {
//we're looking for xxx = staticmethod(xxx)
if (node.targets.length == 1) {
exprType t = node.targets[0];
String rep = NodeUtils.getRepresentationString(t);
if (rep == null) {
return;
}
if (scope.peek() != Scope.SCOPE_TYPE_CLASS) {
//we must be in a class scope
return;
}
Tuple<Expected, FunctionDef> tup = maybeNoSelfDefinedItems.peek().get(rep);
if (tup == null) {
return;
}
FunctionDef def = tup.o2;
if (def == null) {
return;
}
//ok, it may be a staticmethod, let's check its value (should be a call)
exprType expr = node.value;
if (expr instanceof Call) {
Call call = (Call) expr;
if (call.args.length == 1) {
String argRep = NodeUtils.getRepresentationString(call.args[0]);
if (argRep != null && argRep.equals(rep)) {
String funcCall = NodeUtils.getRepresentationString(call.func);
if (def != null && funcCall != null && funcCall.equals("staticmethod")) {
//ok, finally... it is a staticmethod after all...
maybeNoSelfDefinedItems.peek().remove(rep);
} else if (funcCall != null && funcCall.equals("classmethod")) {
//ok, finally... it is a classmethod after all...
tup.o1.expected = "cls";
}
}
}
}
}
}
}